#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <time.h>
#include <elf.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include "cutils.h"
#include "iomem.h"
#include "virtio.h"
#include "machine.h"
#include "riscv_cpu.h"

#define LOW_RAM_SIZE   0x00010000 /* 64KB */
#define RAM_BASE_ADDR  0x80000000
#define DEBUG_BASE_ADDR 0x04000000
#define DEBUG_SIZE 0x1000
#define CLINT_BASE_ADDR 0x02000000
#define CLINT_SIZE      0x000c0000
#define VIRTIO_BASE_ADDR 0x10001000
#define VIRTIO_SIZE      0x1000
#define VIRTIO_IRQ       1
#define PLIC_BASE_ADDR 0x0c000000
#define PLIC_SIZE      0x00400000
#define FRAMEBUFFER_BASE_ADDR 0x41000000
#define RTC_FREQ 10000000
#define RTC_FREQ_DIV 16 /* arbitrary, relative to CPU freq to have a
                           10 MHz frequency */

static uint64_t
rtc_get_real_time () {
    struct timespec ts;
    clock_gettime (CLOCK_MONOTONIC, &ts);
    return (uint64_t)ts.tv_sec * RTC_FREQ +
        (ts.tv_nsec / (1000000000 / RTC_FREQ));
}

static uint64_t
rtc_get_time (RISCVMachine *m) {
    uint64_t val;
    if (m->rtc_real_time) {
        val = rtc_get_real_time () - m->rtc_start_time;
    } else {
        val = riscv_cpu_get_cycles (m->cpu_state) / RTC_FREQ_DIV;
    }
    return val;
}

static uint32_t
clint_read (void *opaque, uint32_t offset, int size_log2) {
    RISCVMachine *m = opaque;
    uint32_t val;
    assert (size_log2 == 2);

    switch (offset)
    {
    case 0xBFF8:
        val = rtc_get_time (m);
        break;
    case 0xBFFC:
        val = rtc_get_time (m) >> 32;
        break;
    case 0x4000:
        val = m->timecmp;
        break;
    case 0x4004:
        val = m->timecmp >> 32;
        break;
    default:
        break;
    }
    return val;
}


static void
clint_write (void *opaque, uint32_t offset, uint32_t val,
             int size_log2) {
    RISCVMachine *m = opaque;
    assert (size_log2 == 2);
    switch (offset)
    {
    case 0x4000:
        m->timecmp = (m->timecmp & ~0xffffffff) | val;
        riscv_cpu_reset_mip (m->cpu_state, MIP_MTIP);
        break;
    case 0x4004:
        m->timecmp = (m->timecmp & 0xffffffff) | ((uint64_t)val << 32);
        riscv_cpu_reset_mip (m->cpu_state, MIP_MTIP);
        break;
    default:
        break;
    }
}

static uint32_t
debug_read (void *opaque, uint32_t offset, int size_log2) {
    (void) opaque;
    (void) offset;
    (void) size_log2;
    return 0;
}

static void
debug_write (void *opaque, uint32_t offset, uint32_t val,
            int size_log2) {
    (void) opaque;
    (void) offset;
    (void) size_log2;
    char c;
    c = (char)val;
    write (STDOUT_FILENO, &c, 1);
    return;
}

static void
plic_update_mip (RISCVMachine *s) {
    RISCVCPUState *cpu = s->cpu_state;
    uint32_t mask;
    mask = s->plic_pending_irq & ~s->plic_served_irq;
    if (mask) {
        riscv_cpu_set_mip (cpu, MIP_MEIP | MIP_SEIP);
    } else {
        riscv_cpu_reset_mip (cpu, MIP_MEIP | MIP_SEIP);
    }
}

#define PLIC_HART_BASE 0x200000
#define PLIC_HART_SIZE 0x1000

static uint32_t
plic_read (void *opaque, uint32_t offset, int size_log2) {
    RISCVMachine *m = opaque;
    uint32_t val, mask;
    int i;
    assert (size_log2 == 2);
    switch (offset)
    {
    case PLIC_HART_BASE:
        val = 0;
        break;
    case PLIC_HART_BASE + 4:
        /* 
            PLIC读取前：pending位为1且served位为0会被serve
            PLIC读取后：served位置1
         */
        mask = m->plic_pending_irq & ~m->plic_served_irq;
        if (mask != 0) {
            i = ctz32 (mask);
            m->plic_served_irq |= 1 << i;
            plic_update_mip (m);
            val = i + 1;
        } else {
            val = 0;
        }
        break;
    default:
        val = 0;
        break;
    }
    return val;
}

static void
plic_write (void *opaque, uint32_t offset, uint32_t val,
            int size_log2) {
    RISCVMachine *s = opaque;
    assert (size_log2 == 2);
    switch (offset) {
    case PLIC_HART_BASE + 4:
        val--;
        if (val < 32) {
            s->plic_served_irq &= ~(1 << val);
            plic_update_mip (s);
        }
        break;
    default:
        break;
    }
}

/* NOTE: 设备通过调用该函数来控制pending_irq 
 * 设备完成后会将pending_irq相应位置1
 * 写入VIRTIO_QUEUE_ACK可将pending_irq相应位清零
 */
static void
plic_set_irq (void *opaque, int irq_num, int state) {
    RISCVMachine *s = opaque;
    uint32_t mask;

    mask = 1 << (irq_num - 1);
    if (state) {
        s->plic_pending_irq |= mask;
    } else {
        s->plic_pending_irq &= ~mask;
    }
    plic_update_mip (s);
}

void
raise_timer_interrupt (RISCVMachine *m) {
    RISCVCPUState *s = m->cpu_state;
    int64_t delay;

    if (!(riscv_cpu_get_mip(s) & MIP_MTIP)) {
        delay = m->timecmp - rtc_get_time(m);
        if (delay <= 0) {
            riscv_cpu_set_mip(s, MIP_MTIP);
        }
    }
}

static BOOL
kernel_ehdr_ok (Elf64_Ehdr *ehdr) {
    if (ehdr->e_ident[EI_MAG0] == ELFMAG0)
    if (ehdr->e_ident[EI_MAG1] == ELFMAG1)
    if (ehdr->e_ident[EI_MAG2] == ELFMAG2)
    if (ehdr->e_ident[EI_MAG3] == ELFMAG3)
    if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)
    if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB)
    if (ehdr->e_type == ET_EXEC)
    if (ehdr->e_machine == EM_RISCV)
    return TRUE;
    return FALSE;
}

static int
load_kernel (PhysMemoryMap *mem_map, const char * k) {
    int elf_fd, i, seg_num;
    PhysMemoryRange *pr;
    uint64_t n, seg_hdr_base, filesz, memsz, virt_off, rdoff, faddr;
    uint8_t *mem_ptr;
    Elf64_Ehdr ehdr_buf;
    Elf64_Phdr phdr_buf;
    if ((elf_fd = open (k, O_RDONLY)) < 0) {
        goto error;
    }
    #define EHDR_SIZE (sizeof (Elf64_Ehdr))
    #define PHDR_SIZE (ehdr_buf.e_phentsize)
    if ((n = read (elf_fd, &ehdr_buf, EHDR_SIZE)) != EHDR_SIZE) {
        goto error;
    }
    if (!kernel_ehdr_ok (&ehdr_buf)) {
        goto error;
    }
    seg_hdr_base = ehdr_buf.e_phoff;
    seg_num = ehdr_buf.e_phnum;
    for (i = 0; i < seg_num; i++) {
        faddr = seg_hdr_base + i * PHDR_SIZE;
        if ((n = pread (elf_fd, &phdr_buf, PHDR_SIZE, faddr)) != PHDR_SIZE) {
            goto error;
        }
        if (phdr_buf.p_type != PT_LOAD) continue;
        filesz = phdr_buf.p_filesz;
        memsz = phdr_buf.p_memsz;
        virt_off = phdr_buf.p_vaddr;
        pr = iomem_get_phys_mem_range (mem_map, virt_off);
        if (!pr || !pr->is_ram) {
            goto error;
        }
        if (pr->addr + pr->size < virt_off + memsz) {
            goto error;
        }
        rdoff = phdr_buf.p_offset;
        mem_ptr = iomem_get_phys_mem_ptr (mem_map, virt_off);
        memset (mem_ptr, 0, memsz);
        if ((n = pread (elf_fd, mem_ptr, filesz, rdoff)) < filesz) {
            goto error;
        }
    }
    close (elf_fd);
    return 0;
    error:
    close (elf_fd);
    return -1;
}

RISCVMachine *
machine_init (const VirtMachineParams *p) {
    RISCVMachine *s;
    int irq_num, i;
    s = mallocz (sizeof (*s));
    VIRTIOBusDef vbus_s, *vbus = &vbus_s;
    s->ram_size = p->ram_size;
    s->mem_map = phys_mem_map_init ();
    s->cs = p->console;
    s->cpu_state = riscv_cpu_init (s->mem_map);

    iomem_register_ram (s->mem_map, RAM_BASE_ADDR, s->ram_size, 0);
    iomem_register_ram (s->mem_map, 0x00000000, LOW_RAM_SIZE, 0);
    s->rtc_real_time = p->real_time; // 使用真实时间
    s->rtc_start_time = rtc_get_real_time ();

    iomem_register_device (s->mem_map, CLINT_BASE_ADDR, CLINT_SIZE, s,
                           clint_read, clint_write, DEVIO_SIZE32);
    iomem_register_device (s->mem_map, PLIC_BASE_ADDR, PLIC_SIZE, s,
                           plic_read, plic_write, DEVIO_SIZE32);
    iomem_register_device (s->mem_map, DEBUG_BASE_ADDR, DEBUG_SIZE, s,
                           debug_read, debug_write, DEVIO_SIZE32);
    
    for (i = 1; i < 32; i++) {
        irq_init (&s->plic_irq[i], plic_set_irq, s, i);
    }

    vbus->mem_map = s->mem_map;
    vbus->addr = VIRTIO_BASE_ADDR;
    irq_num = VIRTIO_IRQ;

    vbus->irq = &s->plic_irq[irq_num];
    s->console = virtio_console_init (vbus, p->console);
    vbus->addr += VIRTIO_SIZE;
    irq_num++;

    vbus->irq = &s->plic_irq[irq_num];
    s->drive = virtio_block_init (vbus, p->drive);
    vbus->addr += VIRTIO_SIZE;
    irq_num++;

    if (load_kernel (s->mem_map, p->kernel) < 0)
        return NULL;

    return s;
}